﻿using EnvDTE;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

namespace Axiom.CodeManifold
{
	public static class CodeModelExtensions
	{
		public static IList<T> GetEntities<T>(this FileCodeModel model) where T : class
		{
			IList<T> result = new List<T>();

			foreach (CodeElement element in model.CodeElements)
			{
				result = GetEntitiesRecoursive<T>(element, result);
			}

			return result;
		}

		private static IList<T> GetEntitiesRecoursive<T>(CodeElement element, IList<T> result) where T : class
		{
			vsCMElement kind = vsCMElement.vsCMElementClass;

			switch (typeof(T).Name)
			{
				case "CodeInterface":
					kind = vsCMElement.vsCMElementInterface;
					break;
				case "CodeNamespace":
					kind = vsCMElement.vsCMElementNamespace;
					break;
				default:
					break;
			}

			if (element.Kind == kind)
			{
				result.Add(element as T);
			}		

			foreach (CodeElement child in element.Children)
			{
				if (child.Kind == kind)
				{
					result.Add(child as T);
				}
				else
				{
					GetEntitiesRecoursive<T>(child, result);
				}
			}

			return result;
		}

		public static T GetEntity<T>(this FileCodeModel model, string name) where T : class
		{
			T result = null;

			foreach (CodeElement element in model.CodeElements)
			{
				result = GetEntityRecoursive<T>(element, name);
				if (result != null)
				{
					break;
				}
			}

			return result;
		}

		private static T GetEntityRecoursive<T>(CodeElement element, string name) where T : class
		{
			T result = null;
			vsCMElement kind = vsCMElement.vsCMElementClass;

			if (typeof(T).Name == "CodeInterface")
			{
				kind = vsCMElement.vsCMElementInterface;
			}

			if (element.Kind == kind)
			{
				result = element as T;
				return result;
			}	

			foreach (CodeElement child in element.Children)
			{
				if (child.Kind == kind && child.Name == name)
				{
					result = child as T;
					break;
				}
				else
				{
					GetEntityRecoursive<T>(child, name);
				}
			}

			return result;
		}

		public static CodeFunction AddMethod(this CodeClass @class, AddMethodRequest request)
		{
			if (@class.GetMethod(request.Method) != null)
			{
				throw new MethodExistsException(string.Format("Cannot add method - Method Signature Already Exists on the {0} Service", request.Method.Aggregate));
			}

			var method = request.Method;
			var missing = Type.Missing;
			var function = @class.AddFunction(method.Name, vsCMFunction.vsCMFunctionFunction, method.ReturnType, -1, vsCMAccess.vsCMAccessPublic, missing);

			foreach (var parameter in request.Method.Parameters)
			{
				function.AddParameter(parameter.Name, parameter.Type, -1);
			}

			function.DocComment = method.DocComment;

			return function;
		}

		public static CodeFunction AddMethod(this CodeInterface @class, AddMethodRequest request)
		{
			if (@class.GetMethod(request.Method) != null)
			{
				throw new MethodExistsException(string.Format("Cannot add method - Method Signature Already Exists: {0}.{1}.{2}", request.Namespace, request.ServiceName, request.Signature));
			}

			var method = request.Method;
			var missing = Type.Missing;
			var function = @class.AddFunction(method.Name, vsCMFunction.vsCMFunctionFunction, method.ReturnType, -1);

			foreach (var parameter in request.Method.Parameters)
			{
				function.AddParameter(parameter.Name, parameter.Type, -1);
			}

			function.DocComment = method.DocComment;

			return function;
		}

		public static CodeFunction UpdateMethod(this CodeClass @class, AddMethodRequest request)
		{
			if (@class.GetMethod(request.Method) != null)
			{
				throw new MethodExistsException(string.Format("Cannot update method - Target Method Signature Already Exists on the {0} Service", request.Method.Aggregate));
			}

			CodeFunction function = @class.GetMethod(request.Method.Memento);
			if (function == null)
			{
				throw new MethodNotFoundException(string.Format("Cannot update method - Target Method Does Not Exists on the {0} Service", request.Method.Aggregate));
			}

			var previousdef = request.Method.Memento;
			var method = request.Method;
			var missing = Type.Missing;

			function.Name = request.Method.Name;

			var parameters = function.Children.OfType<CodeParameter>().ToArray();
			for (int i = parameters.Count() - 1; i >= 0; i--)
			{
				var parameter = parameters[i];
				function.RemoveParameter(parameter);
			}
			foreach (var parameter in request.Method.Parameters)
			{
				CodeParameter p = function.AddParameter(parameter.Name, parameter.Type, -1);
			}

			function.DocComment = request.Method.DocComment;

			return function;
		}

		public static CodeFunction UpdateMethod(this CodeInterface @class, AddMethodRequest request)
		{
			if (@class.GetMethod(request.Method) != null)
			{
				throw new MethodExistsException(string.Format("Cannot update method - Target Method Signature Already Exists on the {0} Service", request.Method.Aggregate));
			}

			CodeFunction function = @class.GetMethod(request.Method.Memento);
			if (function == null)
			{
				throw new MethodNotFoundException(string.Format("Cannot update method - Target Method Does Not Exists on the {0} Service", request.Method.Aggregate));
			}

			var previousdef = request.Method.Memento;
			var method = request.Method;
			var missing = Type.Missing;

			function.Name = request.Method.Name;

			var parameters = function.Children.OfType<CodeParameter>().ToArray();
			for (int i = parameters.Count() - 1; i >= 0; i--)
			{
				var parameter = parameters[i];
				function.RemoveParameter(parameter);
			}
			foreach (var parameter in request.Method.Parameters)
			{
				CodeParameter p = function.AddParameter(parameter.Name, parameter.Type, -1);
			}

			function.DocComment = request.Method.DocComment;

			return function;
		}

		public static CodeFunction GetMethod(this CodeClass @class, MethodDefinition methodDefinition)
		{
			CodeFunction result = null;

			var methods = @class.Children.OfType<CodeFunction>().Where(a => a.Name == methodDefinition.Name);
			foreach (var m in methods)
			{
				var parameters = m.Children.OfType<CodeParameter>().ToArray();

				if (parameters.Count() != methodDefinition.Parameters.Count)
				{
					continue;
				}

				bool match = true;
				for (int i = 0; i < parameters.Count(); i++)
				{
					var parameterA = parameters[i];
					var parameterB = methodDefinition.Parameters[i];
					if (parameterA.Type.GetTypeAsString().ToLower() != parameterB.Type.ToLower())
					{
						match = false;
						break;
					}
				}

				if (match)
				{
					result = m;
					break;
				}
			}

			return result;
		}

		public static CodeFunction GetMethod(this CodeInterface @class, MethodDefinition methodDefinition)
		{
			CodeFunction result = null;

			var methods = @class.Children.OfType<CodeFunction>().Where(a => a.Name == methodDefinition.Name);
			foreach (var m in methods)
			{
				var parameters = m.Children.OfType<CodeParameter>().ToArray();

				if (parameters.Count() != methodDefinition.Parameters.Count)
				{
					continue;
				}

				bool match = true;
				for (int i = 0; i < parameters.Count(); i++)
				{
					var parameterA = parameters[i];
					var parameterB = methodDefinition.Parameters[i];
					if (parameterA.Type.GetTypeAsString().ToLower() != parameterB.Type.ToLower())
					{
						match = false;
						break;
					}
				}

				if (match)
				{
					result = m;
					break;
				}
			}

			return result;
		}

		public static string GetTypeAsString(this CodeTypeRef codeType)
		{
			var fullname = codeType.AsFullName;
			var type = codeType.CodeType.Name;

			Regex regex = new Regex(@"^.*\<(.*)\>$");
			Match m = regex.Match(fullname);
			if (m.Success)
			{
				string gentypes = m.Groups[1].ToString();
				var parts = gentypes.Split(new char[] { ',' });

				var list = new List<string>();
				foreach (var part in parts)
				{
					list.Add(part.Split('.').Last());
				}
				type += "<" + string.Join(", ", list.ToArray()) + ">";
			}

			return type;
		}

		public static MethodDefinition ToMethodDefinition(this CodeFunction function)
		{
			MethodDefinition method = new MethodDefinition()
			{
				Name = function.Name,
				DocComment = function.DocComment,
				Body = function.StartPoint.CreateEditPoint().GetText(function.EndPoint),
				ReturnType = function.Type.CodeType.Name,
				XCodeFunction = new XCodeElement()
				{
					Access = function.Access,
					FunctionKind = function.FunctionKind,
					Type = function.Type,
					CodeType = function.Type.CodeType,
				},
			};
			foreach (var p in function.Children.OfType<CodeParameter>())
			{
				method.Parameters.Add(new Parameter()
				{
					Name = p.Name,
					Type = p.Type.CodeType.Name,
					XCodeFunction = new XCodeElement()
					{
						Type = p.Type,
						CodeType = p.Type.CodeType,
					},
				});
			}

			TextPoint startPoint = function.GetStartPoint(vsCMPart.vsCMPartBody);
			TextPoint endPoint = function.GetEndPoint(vsCMPart.vsCMPartBody);

			var editPoint = startPoint.CreateEditPoint();
			method.Body = editPoint.GetText(endPoint);

			return method;
		}
	}
}
